Skip to content

Derive every editor mutation through one seam so Escape reaches your core#129

Merged
ctate merged 7 commits into
mainfrom
fix/escape-edit-derivation
Jul 16, 2026
Merged

Derive every editor mutation through one seam so Escape reaches your core#129
ctate merged 7 commits into
mainfrom
fix/escape-edit-derivation

Conversation

@ctate

@ctate ctate commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Fixes the launch-round live-smoke finding: pressing Escape in a search field cleared the runtime's text editor visually while the core never heard about it — model.query diverged from the screen, the list stayed filtered under an empty field, and the next keystroke dispatched against the stale query.

The root cause was architectural: some editor mutations derived core events (typing, paste, the clear button) while others were runtime-local operations that bypassed derivation. Mapping every mutation source found five bypassing paths: Escape-clear, Escape composition-cancel, ArrowUp/Down caret moves on single-line kinds, and the automation/AX composition and selection verbs.

  • One choke point: the runtime derives each keyboard editor mutation once and stamps it onto the routed event before applying it; both tiers' app dispatch consume the stamped edit (local derivation kept only as fallback for events that never crossed the runtime). Any future derivation arm automatically reaches the core. Stamping happens even when applying is a no-op, so an already-diverged mirror resyncs.
  • No wire change: the existing clear arm expresses it — Escape now looks to the core exactly like the clear button always did — so the journal format is untouched and replay reproduces the clear byte-identically (the reference record→replay session now drives an Escape-clear and pins identical model state, fingerprints, and per-frame checkpoints).
  • Automation composition verbs now dispatch real IME platform events (journaled — they previously weren't), and set_selection synthesizes the same stamped keyboard event the context-menu edits use.
  • Pinned in both tiers: a runtime test that provably fails without the stamp, the record→replay pins, the Zig soundboard live test, and the soundboard-ts e2e.

Flagged for follow-up (design questions, deliberately not implemented here): pointer-placed selection reaching on_input (needs coalescing), automation set_selection replay honesty (needs a wire-level selection input kind), and the raw editCanvasWidgetText host API's mirror contract.

Suites: validate, test-examples-native, test-ts-core-e2e green; zig build test's five step-runner noise lines reproduce identically on clean main (every flagged binary passes directly).

ctate added 4 commits July 13, 2026 15:33
- updateCanvasWidgetTextFromKeyboard now stamps the edit it derives and applies onto the routed event, so the app dispatch hears exactly what the retained editor did — Escape's search-field clear, its composition cancel, and the single-line ArrowUp/Down caret jumps were runtime-only before and never reached the model.
- The app-side msgForKeyboard consumes the stamped edit first and keeps its own derivation only as the fallback for events that never crossed the runtime.
- The context-menu edit path routes through the same seam and dispatches the stamped event.
- widget-action set_composition/commit_composition/cancel_composition now dispatch the ime gpu input events a live IME session produces: journaled for replay, applied by the editor, and mirrored to the core's on_input — direct editor writes kept the model out of the loop.
- set_selection synthesizes the stamped keyboard event the clipboard edits use, so the core's selection mirror follows; it stays un-journaled (no selection input kind exists on the wire).
- The accessibility action dispatch and the mobile widget-action ABI funnel through the same verbs, so all three surfaces converge.
- ui_app tests drive a search field through Escape, ArrowUp, ime cancel, and the automation composition/selection verbs, asserting the model mirror and the retained editor agree.
- The reference record/replay session types into a search field and Escape-clears it, so replay must re-derive the same clear from the raw journaled events.
- Soundboard e2e batteries (Zig live app and transpiled TS core) pin Escape clearing the query and unfiltering the library.
- User-facing story: Escape in a search field now reaches your core, plus the automation composition verbs riding the real input path.
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
native-sdk Ready Ready Preview, Comment Jul 14, 2026 3:29pm

- Suppress event records staged while a journaled widget_accessibility_action dispatch is on the recorder's staging stack: the verb's synthesized key/text/drop children are deterministic derivations the replayed action re-runs, so recording both dispatched every child twice on replay.
- Effect results still write through mid-action in feed-then-dispatch order, and event_count/checkpoint ordinals stay coherent with the records a replay reader actually sees.
- Pin the class end to end: repeated AX press and set_text plus the composition verbs record and replay with exactly-once input counts and one journal record per action.
- Stage a synthetic widget_accessibility_action inside dispatchCanvasWidgetAccessibilityAction when no platform tag-23 event is on the recorder's staging stack: the direct verb surfaces (embed widgetAction, automation widget_action — now delegated through the same dispatch) journaled only untargeted focus-routed children while the verb's focus write stayed unjournaled, so a first-in-session composition or set_text replayed against the wrong editor or none at all; nested inside a staged platform AX event the synthetic stage is a suppressed placeholder, keeping exactly one record.
- Add the composition kinds to the platform WidgetAccessibilityActionKind enum (values 11-13, additive) with both widget_bridge mappings, so the journal and replay's tag-23 arm carry every verb.
- Pin the class: composition first-in-session, composition while another field holds focus, and set_text first-in-session all record action-only journals and replay onto their target editor; a recorder mechanics test pins the nested placeholder.
- A CLOSED combobox's ArrowUp/Down are its open keys: the app dispatch resolves the press before any stamped edit, so the caret derivation now yields no edit there and the retained editor agrees with the model mirror; an open picker's arrows already walk into the mounted menu, and an expanded trigger without one keeps the caret jump both sides hear.
- Bump the session journal format to v4: this branch serializes the composition action codes 11-13 into accessibility-action records, which a v3 reader would have called corrupt instead of refusing as version skew; the skew message now names the version this build reads, and the skew test pins refusal of older journals too.
- Rewrite the journal's stale coverage note: every automation verb journals now (direct-surface verbs as the outer-wins widget_accessibility_action record), so the v1 do-not-journal caveat is gone.
@ctate ctate merged commit 4d83ce2 into main Jul 16, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant